package com.even.camerarecoder.aac

import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import com.even.camerarecoder.MuxerData
import com.even.camerarecoder.H264ToMp4SaverThread
import com.even.camerarecoder.H264Util
import com.evendai.loglibrary.Timber
import java.nio.ByteBuffer

/**
 * AAC编码器，可以把PCM音频编码AAC
 * @param sampleRateInHz 指定PCM音频数据的采样率
 * @param channelCount 指定PCM音频数据的通道数量
 * @param maxInputSize 指定每次送入AAC编码器的PCM音频数据的最大大小
 */
class AACEncoder(sampleRateInHz: Int, channelCount: Int, maxInputSize: Int) {

    private val mMediaCodec: MediaCodec
    private var inputBufferIndex: Int = MediaCodec.INFO_TRY_AGAIN_LATER
    private var outputBufferIndex: Int = MediaCodec.INFO_TRY_AGAIN_LATER
    private lateinit var inputBuffer: ByteBuffer
    private lateinit var outputBuffer: ByteBuffer
    //指定采样率 （MediaRecorder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100，目前为常用的采样率，官方文档表示这个值可以兼容所有的设备）
    //指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量。AudioFormat.CHANNEL_IN_MONO保证可以在所有设备上使用。
    private val mBitrate = 128000         // 比特率
//    private val mBitrate = 32000    // 比特率
    private var mBufferInfo = MediaCodec.BufferInfo()
    private var mAACSaver: AACSaver = AACSaver()
    // 注：MediaCodec分配的Buffer容量可能比maxInputSize还要大的，所以里的初始化是有问题的，应该根据MediaCodec的Buffer来初始化
    private var mAACWithADTSByteBuffer = ByteBuffer.allocateDirect(maxInputSize + 7)

    private var muxerDataIndex = -1
    private var muxerDataCache: Array<MuxerData>? = null
    private var flags = 0
    /** 表示已经调用了close()方法 */
    private var calledCloseMethod = false
    /** 表示已经调用了releaseMediaCodec()方法 */
    private var calledReleaseMediaCodecMethod = false

    init {
        val mime = MediaFormat.MIMETYPE_AUDIO_AAC
        mMediaCodec = MediaCodec.createEncoderByType(mime)
        val mediaFormat = MediaFormat.createAudioFormat(mime, sampleRateInHz, channelCount)
        mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) // 设置码率为动态码率，默认也是动态码率
        mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC) // 设置profile为AAC LC
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate) // 码率（即比特率）
        // 设置最大输入，这样MediaCodec中分配的inputBuffer的大小应该就会根据这个值来创建。有的手机不设置这个会发现往inputBuffer.put()数据时报异常，因为装的数据太大，而容器没这么大
        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize)

        // 初始化ADTS头信息
        ADTSUtil.initADTS(sampleRateInHz, channelCount)

        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

        mMediaCodec.start()

        H264Util.printSupportedAACEncoder()
        Timber.i("实际使用的AAC编码器：${mMediaCodec.codecInfo.name}")
    }

    /** 把PCM格式的音频数据编码为AAC数据 */
    @Synchronized
    fun encodePCMToAAC(pcmByteBuffer: ByteBuffer) {
        val start = System.nanoTime()
        if (calledReleaseMediaCodecMethod) return // 如果已经释放了编码器，则不处理，因为多线程，所以有可能释放的时候还有数据扔进来编码的
        inputBufferIndex = mMediaCodec.dequeueInputBuffer(10_000) // 如果10毫秒都等不到可用缓冲，则这一帧的yuv数据将丢掉。谷歌官方Demo也是用的这个值
        if (inputBufferIndex >= 0) {
            inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex)!!
            inputBuffer.put(pcmByteBuffer)
            mMediaCodec.queueInputBuffer(
                    inputBufferIndex,
                    0,
                    pcmByteBuffer.limit(),
                    System.nanoTime() / 1000,
                    flags
            )
        }

        outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10_000)
        if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // 注：这里拿到的MediaFormat才是有效的，才可以给MediaMuxer使用
            //CodecData.videoMediaFormat = mMediaCodec.getOutputFormat(outputBufferIndex)
            mediaFormat = mMediaCodec.outputFormat
        }
        while (outputBufferIndex >= 0) {
            when (mBufferInfo.flags) {
                MediaCodec.BUFFER_FLAG_CODEC_CONFIG -> {
                    Timber.i("AAC有配置数据") // 这个配置数据无需写到文件中，不知道干嘛用的
                }
                MediaCodec.BUFFER_FLAG_END_OF_STREAM -> {
                    // TODO 官方写法：mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0
                    // TODO 实际打印mBufferInfo.flags的值就是4，与BUFFER_FLAG_END_OF_STREAM相等，搞清楚为什么官方要这么写
                    Timber.e("已经到达流的终点了, mBufferInfo.flags = ${mBufferInfo.flags}")
                    mMediaCodec.releaseOutputBuffer(outputBufferIndex, false)
                    releaseMediaCodec()
                    break // 退出循环，无需再去获取编码的数据，肯定没有数据了。
                }
                else -> {
                    // 在数组的前面7个字节中填入ADTS头数据
                    mAACWithADTSByteBuffer.clear()
                    ADTSUtil.addADTS(mAACWithADTSByteBuffer, mBufferInfo.size + 7L)

                    // 取出outputBuffer中的AAC数据，并在mAACWithADTSByteBuffer数组索引为7的位置开始填入AAC数据
                    outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex)!!
                    mAACWithADTSByteBuffer.put(outputBuffer)
                    mAACWithADTSByteBuffer.flip()

                    // 把一帧ADTS数据写到文件
                    mAACSaver.saveAAC(mAACWithADTSByteBuffer)

                    H264ToMp4SaverThread.getInstance()?.addH264Data(getMuxerData(outputBuffer, mBufferInfo))
                }
            }

            mMediaCodec.releaseOutputBuffer(outputBufferIndex, false) // 把缓存对象还给MediaCodec
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10_000)
//            Timber.i("编码一帧AAC时间：${String .format("%.2f", (System.nanoTime() -  start) / 1000_000f)}")
        }
    }

    /** 使用此方法创建MuxerData，以实现对象复用 */
    private fun getMuxerData(byteBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo): MuxerData {
        if (muxerDataCache == null) {
            muxerDataCache = Array(cacheSize) {
                MuxerData(false, byteBuffer.capacity())
            }
        }

        if (++muxerDataIndex >= cacheSize) {
            muxerDataIndex = 0
        }

        return muxerDataCache!![muxerDataIndex].apply {
            copyFrom(byteBuffer, bufferInfo)
        }
    }

    @Synchronized
    fun close() {
        try {
            Timber.i("close")
            if (calledCloseMethod) return // 预防关闭函数被调用两次
            calledCloseMethod = true
            flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM
            encodePCMToAAC(ByteBuffer.allocateDirect(0))
        } catch (e: Exception) {
            Timber.fe(e,"关闭AAC编码器时出现异常")
        }
    }

    private fun releaseMediaCodec() {
        calledReleaseMediaCodecMethod = true

        try {
            mMediaCodec.stop()
        } catch (e: Exception) {
            Timber.e(e, "，别慌，正常停止${AACEncoder::class.java.simpleName}时出现的异常！")
        }

        try {
            mMediaCodec.release()
        } catch (e: Exception) {
            Timber.e(e, "，别慌，正常释放${AACEncoder::class.java.simpleName}时出现的异常！")
        }

        try {
            mAACSaver.close()
        } catch (e: Exception) {
            Timber.fe(e, "关闭${AACSaver::class.java.simpleName}时出现异常！")
        }

        mediaFormat = null
        muxerDataCache = null
        muxerDataIndex = -1
    }

    companion object {
        var mediaFormat: MediaFormat? = null
        var cacheSize = 3
    }

}